/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
 * http://www.gnu.org/licenses/lgpl-3.0.html
 *
 * $Revision: 11183 $
 * $Id: editorbase.cpp 11183 2017-10-05 22:33:14Z fuscated $
 * $HeadURL: http://svn.code.sf.net/p/codeblocks/code/branches/release-17.xx/src/sdk/editorbase.cpp $
 */

#include "sdk_precomp.h"

#ifndef CB_PRECOMP
    #include <wx/filename.h>
    #include <wx/notebook.h>
    #include <wx/menu.h>
    #include "manager.h"
    #include "editorbase.h"
    #include "cbeditor.h"
    #include "editormanager.h"
    #include "pluginmanager.h"
/*引用文件树数据(FileTreeData)*/
    #include "cbproject.h"
/*引用项目数组(ProjectsArray)*/
    #include "projectmanager.h"
    #include <wx/wfstream.h>
#endif

#include "cbauibook.h"
#include "cbstyledtextctrl.h"

/*变量初始化所需*/
inline int editorbase_RegisterId(int id)
{
    wxRegisterId(id);
    return id;
}

struct EditorBaseInternalData
{
    EditorBaseInternalData(EditorBase* owner)
        : m_pOwner(owner),
        m_DisplayingPopupMenu(false),
        m_CloseMe(false)
    {}

    EditorBase* m_pOwner;
    bool m_DisplayingPopupMenu;
    bool m_CloseMe;
};

/*以下行保留255个连续的id*/
const int EditorMaxSwitchTo = 255;
const int idSwitchFile1 = wxNewId();
const int idSwitchFileMax = editorbase_RegisterId(idSwitchFile1 + EditorMaxSwitchTo -1);

const int idCloseMe = wxNewId();
const int idCloseAll = wxNewId();
const int idCloseAllOthers = wxNewId();
const int idSaveMe = wxNewId();
const int idSaveAll = wxNewId();
const int idSwitchTo = wxNewId();

/*搜索...在上下文菜单中,用CTRL+右键单击可见*/
const long idGoogle        = wxNewId();
const long idMsdn          = wxNewId();
const long idStackOverflow = wxNewId();
const long idCodeProject   = wxNewId();
const long idCPlusPlusCom  = wxNewId();

BEGIN_EVENT_TABLE(EditorBase, wxPanel)
    EVT_MENU_RANGE(idSwitchFile1, idSwitchFileMax, EditorBase::OnContextMenuEntry)
    EVT_MENU(idCloseMe,           EditorBase::OnContextMenuEntry)
    EVT_MENU(idCloseAll,          EditorBase::OnContextMenuEntry)
    EVT_MENU(idCloseAllOthers,    EditorBase::OnContextMenuEntry)
    EVT_MENU(idSaveMe,            EditorBase::OnContextMenuEntry)
    EVT_MENU(idSaveAll,           EditorBase::OnContextMenuEntry)

    EVT_MENU(idGoogle,            EditorBase::OnContextMenuEntry)
    EVT_MENU(idMsdn,              EditorBase::OnContextMenuEntry)
    EVT_MENU(idStackOverflow,     EditorBase::OnContextMenuEntry)
    EVT_MENU(idCodeProject,       EditorBase::OnContextMenuEntry)
    EVT_MENU(idCPlusPlusCom,      EditorBase::OnContextMenuEntry)
END_EVENT_TABLE()

void EditorBase::InitFilename(const wxString& filename)
{
    if (filename.IsEmpty())
        m_Filename = realpath(CreateUniqueFilename());
    else
        m_Filename = realpath(filename);

    wxFileName fname;
    fname.Assign(m_Filename);
    m_Shortname = fname.GetFullName();
}

wxString EditorBase::CreateUniqueFilename()
{
    const wxString prefix = wxT("Untitled");
    const wxString path = wxGetCwd() + wxFILE_SEP_PATH;
    wxString tmp;
    int iter = 0;
    while (true)
    {
        tmp.Clear();
        tmp << path << prefix << wxString::Format(wxT("%d"), iter);
        if (!Manager::Get()->GetEditorManager()->GetEditor(tmp) &&
                !wxFileExists(path + tmp))
        {
            return tmp;
        }
        ++iter;
    }
}

EditorBase::EditorBase(wxWindow* parent, const wxString& filename)
        : wxPanel(parent, -1),
        m_IsBuiltinEditor(false),
        m_Shortname(_T("")),
        m_Filename(_T("")),
        m_WinTitle(filename)
{
    m_pData = new EditorBaseInternalData(this);

    Manager::Get()->GetEditorManager()->AddCustomEditor(this);
    InitFilename(filename);
    SetTitle(m_Shortname);
}

EditorBase::~EditorBase()
{
    if (!Manager::Get()->IsAppShuttingDown())
    {
        Manager::Get()->GetEditorManager()->RemoveCustomEditor(this);

        CodeBlocksEvent event(cbEVT_EDITOR_CLOSE);
        event.SetEditor(this);
        event.SetString(m_Filename);

        Manager::Get()->GetPluginManager()->NotifyPlugins(event);
    }
    delete m_pData;
}

const wxString& EditorBase::GetTitle()
{
    return m_WinTitle;
}

/*编辑器基类->设置标题*/
void EditorBase::SetTitle(const wxString& newTitle)
{
    m_WinTitle = newTitle;
    int mypage = Manager::Get()->GetEditorManager()->FindPageFromEditor(this);
    if (mypage != -1)
        Manager::Get()->GetEditorManager()->GetNotebook()->SetPageText(mypage, newTitle);

    /*将完整文件名(包括路径)设置为工具提示,如果可能,还可以添加适当的项目*/
    wxString toolTip = GetFilename();
    wxFileName fname(realpath(toolTip));
    NormalizePath(fname, wxEmptyString);
    toolTip = UnixFilename(fname.GetFullPath());

    cbProject* prj = Manager::Get()->GetProjectManager()->FindProjectForFile(toolTip, nullptr, false, true);
    if (prj) { toolTip += wxT("\n项目: ") + prj->GetTitle(); }
    /*当前所在页*/
    cbAuiNotebook* nb = Manager::Get()->GetEditorManager()->GetNotebook();
    if (nb)
    {
        int idx = nb->GetPageIndex(this);
        nb->SetPageToolTip(idx, toolTip);
        Manager::Get()->GetEditorManager()->MarkReadOnly(idx, IsReadOnly() || (fname.FileExists() && !wxFile::Access(fname.GetFullPath(), wxFile::write)) );
    }
}

void EditorBase::Activate()
{
    Manager::Get()->GetEditorManager()->SetActiveEditor(this);
}

bool EditorBase::QueryClose()
{
    if ( GetModified() )
    {
        wxString msg;
        msg.Printf(wxT("文件%s已修改...\n是否保存更改?"), GetFilename().wx_str());
        switch (cbMessageBox(msg, wxT("保存文件"), wxICON_QUESTION | wxYES_NO | wxCANCEL))
        {
            case wxID_YES:
                if (!Save())
                    return false;
                break;
            case wxID_NO:
                break;
            case wxID_CANCEL:
            default:
                return false;
        }
        SetModified(false);
    }
    return true;
}

bool EditorBase::Close()
{
    Destroy();
    return true;
}

bool EditorBase::IsBuiltinEditor() const
{
    return m_IsBuiltinEditor;
}

bool EditorBase::ThereAreOthers() const
{
    return (Manager::Get()->GetEditorManager()->GetEditorsCount() > 1);
}

/*用于上下文菜单*/
wxMenu* EditorBase::CreateContextSubMenu(long id)
{
    wxMenu* menu = nullptr;

    if (id == idSwitchTo)
    {
        menu = new wxMenu;
        m_SwitchTo.clear();
        for (int i = 0; i < EditorMaxSwitchTo && i < Manager::Get()->GetEditorManager()->GetEditorsCount(); ++i)
        {
            EditorBase* other = Manager::Get()->GetEditorManager()->GetEditor(i);
            if (!other || other == this)
                continue;
            id = idSwitchFile1+i;
            m_SwitchTo[id] = other;
            menu->Append(id, (other->GetModified() ? wxT("*") : wxEmptyString) + other->GetShortName());
        }
        if (!menu->GetMenuItemCount())
        {
            delete menu;
            menu = nullptr;
        }
    }
    return menu;
}

void EditorBase::BasicAddToContextMenu(wxMenu* popup, ModuleType type)
{
    if (type == mtOpenFilesList)
    {
      popup->Append(idCloseMe, wxT("关闭"));
      popup->Append(idCloseAll, wxT("关闭所有"));
      popup->Append(idCloseAllOthers, wxT("关闭其他"));
      popup->AppendSeparator();
      popup->Append(idSaveMe, wxT("保存"));
      popup->Append(idSaveAll, wxT("保存所有"));
      popup->AppendSeparator();
      /*根据状态启用/禁用某些项*/
      popup->Enable(idSaveMe, GetModified());

      bool hasOthers = ThereAreOthers();
      popup->Enable(idCloseAll, hasOthers);
      popup->Enable(idCloseAllOthers, hasOthers);
    }
    /*没编辑器*/
    if (type != mtEditorManager)
    {
        wxMenu* switchto = CreateContextSubMenu(idSwitchTo);
        if (switchto)
            popup->Append(idSwitchTo, wxT("切换到"), switchto);
    }
}

void EditorBase::DisplayContextMenu(const wxPoint& position, ModuleType type)
{
    bool noeditor = (type != mtEditorManager);
    /*没有编辑器:如果上下文菜单属于打开的文件树,则为True,如果属于cbEditor,则返回False,通知编辑器我们正要创建上下文菜单*/
    if (!OnBeforeBuildContextMenu(position, type)) { return; }

    wxMenu* popup = new wxMenu;

    if (!noeditor && wxGetKeyState(WXK_CONTROL))
    {
        cbStyledTextCtrl* control = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor()->GetControl();
        wxString text = control->GetSelectedText();
        if (text.IsEmpty())
        {
            const int pos = control->GetCurrentPos();
            text = control->GetTextRange(control->WordStartPosition(pos, true), control->WordEndPosition(pos, true));
        }

        if (!text.IsEmpty())
        {
            popup->Append(idGoogle,        wxT("在互联网上搜索 \"")  + text + _T("\""));
            popup->Append(idMsdn,          wxT("在MSDN上搜索 \"")          + text + _T("\""));
            popup->Append(idStackOverflow, wxT("在StackOverflow上搜索 \"") + text + _T("\""));
            popup->Append(idCodeProject,   wxT("在CodeProject上搜索 \"")   + text + _T("\""));
            popup->Append(idCPlusPlusCom,  wxT("在CplusPlus.com上搜索 \"") + text + _T("\""));
        }
        lastWord = text;

        wxMenu* switchto = CreateContextSubMenu(idSwitchTo);
        if (switchto)
        {
            if (popup->GetMenuItemCount() > 0)
                popup->AppendSeparator();
            popup->Append(idSwitchTo, wxT("切换到"), switchto);
        }
    }else if (!noeditor && wxGetKeyState(WXK_ALT)){
        /*运行一个脚本*/
    }else{
        /*基本函数*/
        BasicAddToContextMenu(popup, type);
        /*扩展函数,第1部分(虚拟)*/
        AddToContextMenu(popup, type, false);
        /*询问其他编辑器/插件是否需要在此菜单中添加任何项...*/
        FileTreeData* ftd = new FileTreeData(nullptr, FileTreeData::ftdkUndefined);
        ftd->SetFolder(m_Filename);
        Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu(type, popup, ftd);
        delete ftd;
        popup->AppendSeparator();
        /*扩展函数,第2部分(虚拟)*/
        AddToContextMenu(popup, type, true);
    }
    /*通知编辑器我们已经创建了上下文菜单(即将显示)*/
    OnAfterBuildContextMenu(type);
    /*显示菜单*/
    wxPoint clientpos;
    /*"上下文菜单"键*/
    if (position==wxDefaultPosition)
    {
        /*获取插入符号点(在屏幕上),因为我们假设用户想要使用键盘*/
        cbStyledTextCtrl* const control = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor()->GetControl();
        clientpos = control->PointFromPosition(control->GetCurrentPos());
    }
    else
    {
        clientpos = ScreenToClient(position);
    }

    m_pData->m_DisplayingPopupMenu = true;
    PopupMenu(popup, clientpos);
    delete popup;
    m_pData->m_DisplayingPopupMenu = false;

    /*此代码必须是此函数执行的最后一个代码,因为它将使"this"无效*/
    if (m_pData->m_CloseMe) { Manager::Get()->GetEditorManager()->Close(this); }
}

void EditorBase::OnContextMenuEntry(wxCommandEvent& event)
{
    /*对于所有弹出菜单项,我们都有一个事件处理程序.这是从cbEditor移植的,用于基本操作：切换,关闭,保存等.*/
    const int id = event.GetId();
    m_pData->m_CloseMe = false;

    if (id == idCloseMe)
    {
        /*延迟删除"this",直到PopupMenu()调用返回*/
        if (m_pData->m_DisplayingPopupMenu)
            m_pData->m_CloseMe = true;
        else
            Manager::Get()->GetEditorManager()->Close(this);
    }
    else if (id == idCloseAll)
    {
        if (m_pData->m_DisplayingPopupMenu)
        {
            Manager::Get()->GetEditorManager()->CloseAllInTabCtrlExcept(this);
            /*延迟删除"this",直到PopupMenu()调用返回*/
            m_pData->m_CloseMe = true;
        }
        else
            Manager::Get()->GetEditorManager()->CloseAllInTabCtrl();
    }
    else if (id == idCloseAllOthers)
    {
        Manager::Get()->GetEditorManager()->CloseAllInTabCtrlExcept(this);
    }
    else if (id == idSaveMe)
    {
        Save();
    }
    else if (id == idSaveAll)
    {
        Manager::Get()->GetEditorManager()->SaveAll();
    }
    else if (id >= idSwitchFile1 && id <= idSwitchFileMax)
    {
        /*切换到某菜单项*/
        EditorBase* const ed = m_SwitchTo[id];
        if (ed) { Manager::Get()->GetEditorManager()->SetActiveEditor(ed); }

        m_SwitchTo.clear();
    }
    else
    {
        if      (id == idGoogle)
            wxLaunchDefaultBrowser(wxString(_T("http://www.google.com/search?q="))                       << URLEncode(lastWord));
        else if (id == idMsdn)
            wxLaunchDefaultBrowser(wxString(_T("http://social.msdn.microsoft.com/Search/en-US/?query=")) << URLEncode(lastWord) << _T("&ac=8"));
        else if (id == idStackOverflow)
            wxLaunchDefaultBrowser(wxString(_T("http://stackoverflow.com/search?q="))                    << URLEncode(lastWord));
        else if (id == idCodeProject)
            wxLaunchDefaultBrowser(wxString(_T("http://www.codeproject.com/search.aspx?q="))             << URLEncode(lastWord));
        else if (id == idCPlusPlusCom)
            wxLaunchDefaultBrowser(wxString(_T("http://www.cplusplus.com/search.do?q="))                 << URLEncode(lastWord));
    }
}

bool EditorBase::IsContextMenuOpened() const
{
    return m_pData->m_DisplayingPopupMenu;
}
